package com.freewater.javabase.demo.mutithread.virtualthread;

import com.google.common.collect.Lists;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.locks.LockSupport;

/**
 * <p>
 * Thread API Changes
 * After a series of experiments with different APIs, the designers of Java virtual threads decided to simply reuse the familiar Thread API. A virtual thread is an instance of Thread. Cancellation works the same way as for platform threads, by calling interrupt. As always, the thread code must check the “interrupted” flag or call a method that does. (Most blocking methods do.)
 * </p>
 * <p>
 * There are a few differences. In particular, all virtual threads:
 * </p>
 * <p>
 * Are in a single thread group
 * Have priority NORM_PRIORITY
 * Are daemon threads
 * </p>
 * <p>
 * There is no API for constructing a virtual thread with another thread group. Trying to call setPriority or setDaemon on a virtual thread has no effect.
 * </p>
 * <p>
 * The static Thread::getAllStackTraces method returns a map of stack traces of all platform threads. Virtual threads are not included.
 * </p>
 * <p>
 * A new Thread::isVirtual instance method tells whether a thread is virtual.
 * </p>
 * <p>
 * Note that there is no way to find the platform thread on which a virtual thread executes.
 * </p>
 * <p>
 * Java 19 has a couple of changes to the Thread API that have nothing to do with virtual threads:
 * </p>
 * <p>
 * There are now instance methods join(Duration) and sleep(Duration).
 * The non-final getId method is deprecated since someone might override it to return something other than the thread ID. Call the final threadId method instead.
 * As of Java 20, the stop, suspend, and resume methods throw an UnsupportedOperationException for both platform and virtual threads. These methods have been deprecated since Java 1.2 and deprecated for removal since Java 18.
 * </p>
 */
public class VirtualThreadDemo {
    public static void main(String[] args) {
        demo1();
        demo2();
        demo3();
        demo4();
        demo5();
    }

    private static void demo1() {
        final int NTASKS = 100;
        ExecutorService service = Executors.newVirtualThreadPerTaskExecutor();
        for (int i = 0; i < NTASKS; i++) {
            service.submit(getTask());
        }
        service.close();
    }

    /**
     * You can also use a builder to create a single virtual thread:
     * <pre>
     * Thread t = builder.unstarted(myRunnable);
     * </pre>
     * Alternatively, if you want to start the thread right away:
     * <pre>
     * Thread t = builder.started(myRunnable);
     * </pre>
     * Finally, for a quick demo, there is a convenience method:
     * <pre>
     * Thread t = Thread.startVirtualThread(myRunnable);
     * </pre>
     * Note that only the first approach(demo1), with an executor service, works with result-bearing tasks (callables).
     */
    private static void demo2() {
        //  The name method configures the builder to set thread names request-1, request-2, and so on.
        Thread.Builder builder = Thread.ofVirtual().name("request-", 1);
        ThreadFactory threadFactory = builder.factory();
        final int NTASKS = 100;
        List<Thread> threads = Lists.newArrayList();
        for (int i = 0; i < NTASKS; i++) {
            // calling factory.newThread(myRunnable) creates a new (unstarted) virtual thread
            Thread thread = threadFactory.newThread(getTask());
            threads.add(thread);
        }
        threads.forEach(Thread::start);
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static void demo3() {
        //  The name method configures the builder to set thread names request-1, request-2, and so on.
        Thread.Builder builder = Thread.ofVirtual().name("request-", 1);
        final int NTASKS = 100;
        List<Thread> threads = Lists.newArrayList();
        for (int i = 0; i < NTASKS; i++) {
            // calling builder.unstarted(myRunnable) creates a new (unstarted) virtual thread
            Thread thread = builder.unstarted(getTask());
            threads.add(thread);
        }
        threads.forEach(Thread::start);
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static void demo4() {
        //  The name method configures the builder to set thread names request-1, request-2, and so on.
        Thread.Builder builder = Thread.ofVirtual().name("request-", 1);
        final int NTASKS = 100;
        List<Thread> threads = Lists.newArrayList();
        for (int i = 0; i < NTASKS; i++) {
            // calling builder.start(myRunnable) creates a new (started) virtual thread
            Thread thread = builder.start(getTask());
            threads.add(thread);
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static void demo5() {
        //  The name method configures the builder to set thread names request-1, request-2, and so on.
        final int NTASKS = 100;
        List<Thread> threads = Lists.newArrayList();
        for (int i = 0; i < NTASKS; i++) {
            // calling Thread.startVirtualThread(myRunnable) creates a new (started) virtual thread
            Thread thread = Thread.startVirtualThread(getTask());
            threads.add(thread);
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static Runnable getTask() {
        return () -> {
            long id = Thread.currentThread().threadId();
            // By the way, the code uses LockSupport.parkNanos instead of Thread.sleep
            // so that we don't have to catch the pesky InterruptedException.
            LockSupport.parkNanos(1_000_000_000);
            System.out.println(id);
        };
    }
}
